using System;
using System.Collections.Generic;
using System.Linq;
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else
using System.Windows;
#endif

namespace FreshMeat.Xaml
{
   public class AttachedBehaviorRegistry
   {
      readonly Dictionary<Type, List<AttachedBehavior>> instancesByType = new Dictionary<Type, List<AttachedBehavior>>();
      const int DefaultCreatesToCleanupRatio = 20;
      int createdCount;

      /// <summary>
      /// Gets an existing instance or creates a new instance of the behavior attached the associated object.
      /// </summary>
      public AttachedBehavior GetInstance(Type behaviorType, DependencyObject associatedObject)
      {
         List<AttachedBehavior> instances;
         if (!instancesByType.TryGetValue(behaviorType, out instances))
         {
            instances = new List<AttachedBehavior>();
            instancesByType.Add(behaviorType, instances);
         }

         // Doesn't seem like there's any way around having to do a linear search here
         // because we can't use the associated object as the key in a dictionary because 
         // then the dictionary would have a hard reference to it, which we don't want.
#if SILVERLIGHT
         // Silverlight's List<> doesn't have a Find so we use Linq.
         var instance = instances.FirstOrDefault(item => item.TryGetAssociatedObject() == associatedObject);
#else
         // This is more effecient when it exists; ironically, the effeciency barely matters in a native app but matters a lot in Silverlight, where they left out this efficient approach...
         var instance = instances.Find(item => item.TryGetAssociatedObject() == associatedObject);
#endif
         if (instance == null)
         {
            instance = (AttachedBehavior)Activator.CreateInstance(behaviorType);
            instance.Attach(associatedObject);

            instances.Add(instance);

            // Whenever we've created a certain number of new instances, we trigger a cleanup of old instances.
            if (++createdCount == CreatesToCleanupRatio)
            {
               createdCount = 0;
               Cleanup();
            }
         }

         return instance;
      }

      /// <summary>
      /// Releases the instance if it exists; otherwise, does nothing.
      /// </summary>
      /// <param name="instance"></param>
      public bool ReleaseInstance(AttachedBehavior instance)
      {
         // Detach but tell it not to release since we're already releasing it
         instance.Detach(false);

         var behaviorType = instance.GetType();

         // Remove it if it exists (behaviors are cleaned up periodically so it may not longer exist).
         List<AttachedBehavior> instances;
         return instancesByType.TryGetValue(behaviorType, out instances) && instances.Remove(instance);
      }

      public void Cleanup()
      {
         CleanupCount++;

         // Remove all instances whose associated object has been garbage collected.
         // Do NOT call instance.Detach since it calls ReleaseInstance -- a loop. :)
         foreach (var instances in instancesByType.Values)
            instances.RemoveAll(instance =>
                                {
                                   if (instance.TryGetAssociatedObject() != null)
                                      return false;
                                   // Detach but tell it not to release since we're already releasing it
                                   instance.Detach(false);
                                   return true;
                                });
      }

      public int CreatesToCleanupRatio
      {
         get { return DefaultCreatesToCleanupRatio; }
      }

      public int CleanupCount { get; private set; }

      public int TypeCount
      {
         get { return instancesByType.Keys.Count; }
      }

      public int InstanceCount
      {
         get { return instancesByType.Values.Sum(instances => instances.Count); }
      }
   }
}